# src/__main__.py
import argparse
import sys
import os
import shlex

from .analyzer import (
    analyze_project, analyze_project_with_mode, detect_all_entries, 
    extract_go_version, validate_entry, prioritize_entries, should_skip_probe
)
from .context import prepare_context
from .dockerfile import generate_dockerfile
from .builder import build_image, read_ldd_from_image, probe_runtime_deps, export_image
from .runtime_map import aggregate_runtime_packages_from_probe
from .project_installer import detect_project_installer
from .security import SecurityScanner

# 将 _print_analysis 函数放在文件顶部，确保可用
def _print_analysis(info: dict):
    """打印分析信息摘要"""
    def _arr(a): return ", ".join(a) if a else "-"
    print("\n===== 依赖分析摘要 =====")
    print(f"项目根: {info.get('go_path', '-')}")  # 项目根目录
    print(f"入口(宿主): {info.get('entry','-')}")  # 宿主机上的入口文件
    print(f"入口(容器相对): {info.get('entry_rel','-')}")  # 容器内的入口文件路径
    print(f"使用CGO: {info.get('use_cgo', False)}")  # 是否使用CGO
    print(f"CGO 库: {_arr(info.get('cgo_libs', []))}")  # CGO库
    print(f"CGO 包含目录(-I): {_arr(info.get('cgo_include_paths', []))}")  # CGO包含路径
    print(f"CGO 库目录(-L): {_arr(info.get('cgo_lib_paths', []))}")  # CGO库路径
    print(f"pkg-config 模块: {_arr(info.get('cgo_pkg_config_modules', []))}")  # pkg-config模块
    print(f"检测到的外部工具: {_arr(info.get('tools_detected', []))}")  # 检测到的外部工具
    print(f"建议安装的系统包(构建期): {_arr(info.get('sys_packages_suggested', []))}")  # 建议安装的系统包
    print(f"按扩展名发现的配置/资源文件数量: {len(info.get('configs', []))}")  # 配置文件数量
    print(f"代码中显式路径(可能需要复制)数量: {len(info.get('resource_paths', []))}")  # 代码中的资源路径
    uses_embed = info.get('uses_embed', False)
    print(f"go:embed 使用: {uses_embed}  模式: {_arr(info.get('embed_patterns', []))}")  # go:embed使用情况
    print(f"Go 版本: {info.get('go_version', '未指定')}")  # 输出Go版本


def _print_mode_info(build_mode: str, project_info: dict):
    """打印模式相关信息"""
    mode_descriptions = {
        "lightweight": "轻量模式 - 最小化镜像大小，适合生产环境",
        "standard": "标准模式 - 平衡功能和大小，适合一般使用",
        "debug": "调试模式 - 包含调试工具，适合开发调试"
    }
    
    print(f"\n===== 构建模式信息 =====")
    print(f"当前模式: {build_mode}")
    print(f"描述: {mode_descriptions.get(build_mode, '未知模式')}")
    
    estimated_size = _estimate_image_size(build_mode, project_info)
    print(f"预估镜像大小: {estimated_size}")


def _estimate_image_size(build_mode: str, project_info: dict) -> str:
    """估算镜像大小"""
    base_sizes = {
        "lightweight": 15,  # MB
        "standard": 80,
        "debug": 300
    }
    
    base_size = base_sizes.get(build_mode, 80)
    
    # 根据项目特征调整
    if project_info.get("use_cgo"):
        base_size += 20
    
    binary_count = len(project_info.get("entries", [1]))
    base_size += binary_count * 5
    
    return f"约 {base_size}MB"


def _parse_probe_args_default(_bin_name: str):
    """动态探测时使用的默认参数"""
    return ["--help"]


def main():
    parser = argparse.ArgumentParser(
        description="Go 应用容器化工具 for OpenCloudOS（docker 构建）"
    )
    parser.add_argument("project_path", help="Go 项目根目录路径 (e.g., /path/to/go/project)")
    parser.add_argument("--image", help="输出镜像名:tag (默认: <项目目录名>:latest)")
    parser.add_argument("--force-probe", action="store_true",
                        help="即使未启用 CGO，也强制执行运行时动态探测")
    parser.add_argument("--no-build", action="store_true",
                        help="只生成 Dockerfile 和分析报告，不执行 docker 构建")
    parser.add_argument("--print-dockerfile", action="store_true",
                        help="在控制台打印生成的 Dockerfile 内容")
    parser.add_argument(
        "--probe-args",
        default="--help",
        help="运行期动态探测时传给每个可执行程序的参数（默认: --help），如：--probe-args='--version' 或 '--probe-args=\"-v --json\"'"
    )
    parser.add_argument(
        "--prefer-project-installer",
        action="store_true",
        help="若项目包含自带安装方法（Makefile install / install.sh），优先使用其方法并跳过分析/探测"
    )
    # 新增：手动指定入口参数
    parser.add_argument(
        "--entry",
        help="手动指定主入口文件路径（相对于项目根目录），例如: cmd/server/main.go"
    )
    parser.add_argument(
        "--single-binary",
        action="store_true",
        help="只构建指定的单个入口，不构建所有发现的入口"
    )
    # 导出镜像文件参数
    parser.add_argument(
        "--export-image",
        action="store_true",
        help="构建完成后导出镜像为 tar 文件"
    )
    parser.add_argument(
        "--export-path",
        help="镜像文件导出路径（默认：当前目录下的 <镜像名>.tar）"
    )
    # 安全扫描参数
    parser.add_argument(
        "--security-scan",
        action="store_true",
        help="启用安全扫描功能"
    )
    parser.add_argument(
        "--security-report-path",
        help="安全报告输出路径（默认：当前目录下的 <镜像名>_security_report.md）"
    )
    # 镜像大小模式参数
    parser.add_argument(
        "--build-mode",
        choices=["lightweight", "standard", "debug"],
        default="standard",
        help="构建模式：lightweight(轻量模式), standard(标准模式), debug(调试模式)"
    )

    args = parser.parse_args()

    # 镜像名
    project_name = os.path.basename(os.path.abspath(args.project_path).rstrip("/"))
    image_name = args.image if args.image else f"{project_name}:latest"

    # 获取 go.mod 文件中的 Go 版本
    go_mod_path = os.path.join(args.project_path, "go.mod")
    go_version = extract_go_version(go_mod_path)  # 获取 go.mod 中的 Go 版本

    # 入口发现和选择
    entries = detect_all_entries(args.project_path)
    if not entries:
        print("❌ 未检测到可执行入口（package main + func main）。该项目可能是库，无法直接容器化。")
        sys.exit(1)

    # 手动指定入口逻辑
    if args.entry:
        manual_entry = os.path.join(args.project_path, args.entry)
        if validate_entry(manual_entry):
            print(f"🎯 使用手动指定的入口: {args.entry}")
            primary_entry = manual_entry
            
            # 查找匹配的入口或创建新的
            matching_entry = None
            for e in entries:
                if e['abs'] == manual_entry:
                    matching_entry = e
                    break
            
            if matching_entry:
                if args.single_binary:
                    build_set = [matching_entry]
                    print(f"🔧 单入口模式：只构建 {matching_entry['out_rel']}")
                else:
                    build_set = entries
                    # 将指定入口移到第一位
                    entries = [matching_entry] + [e for e in entries if e['abs'] != manual_entry]
                    build_set = entries
            else:
                # 手动指定的入口不在自动发现列表中，创建一个
                entry_rel = os.path.relpath(manual_entry, args.project_path)
                dir_rel = os.path.dirname(entry_rel)
                manual_entry_info = {
                    "name": os.path.basename(dir_rel) if dir_rel else "app",
                    "abs": manual_entry,
                    "rel": entry_rel.replace("\\", "/"),
                    "out_rel": dir_rel.replace("\\", "/") if dir_rel else "app",
                    "bin_name": os.path.basename(dir_rel) if dir_rel else "app"
                }
                
                if args.single_binary:
                    build_set = [manual_entry_info]
                    print(f"🔧 单入口模式：只构建 {manual_entry_info['out_rel']}")
                else:
                    build_set = [manual_entry_info] + entries
                    
            default_bin = build_set[0]["out_rel"]
        else:
            print(f"❌ 指定的入口文件无效或不是可执行入口: {args.entry}")
            print("   请检查文件是否存在且包含 'package main' 和 'func main'")
            sys.exit(1)
    else:
        # 自动选择：优先级排序后选择第一个
        entries = prioritize_entries(entries)
        build_set = entries
        primary_entry = entries[0]["abs"]
        default_bin = entries[0]["out_rel"]
        
        print(f"🔍 发现 {len(entries)} 个入口，已按优先级排序")
        if len(entries) > 1:
            print(f"🎯 主入口: {entries[0]['out_rel']} (如需指定其他入口，使用 --entry 参数)")
            print(f"📋 其他入口: {', '.join(e['out_rel'] for e in entries[1:6])}" + 
                  ("..." if len(entries) > 6 else ""))

    # 显示构建信息
    if len(build_set) == 1:
        print(f"🔨 将构建单个入口: {build_set[0]['out_rel']}")
    else:
        print(f"🔨 将构建 {len(build_set)} 个入口：{', '.join(e['out_rel'] for e in build_set[:5])}" + 
              ("..." if len(build_set) > 5 else ""))
    print(f"🚀 主入口文件: {primary_entry}")

    # 自带安装器探测
    installer_plan = detect_project_installer(args.project_path)
    use_installer_mode = bool(installer_plan)

    # 输出检测结果
    if installer_plan:
        print(f"检测到自带安装工具: {installer_plan}")
    else:
        print("未检测到自带安装工具")

    if use_installer_mode:
        print("⚙️ 检测到项目自带安装方法，将跳过静态/动态分析，直接使用其安装流程。")
        print(f"   安装命令：{installer_plan['installer_cmd']}（容器内将自动剥离 sudo）")

        # 仅为模板准备最小上下文（不做 analyze_project）
        project_info = {
            "go_path": os.path.abspath(args.project_path),
            "entry": primary_entry,
            "entry_rel": os.path.relpath(primary_entry, os.path.abspath(args.project_path)),
            "use_cgo": True,  # 大多需要 CGO（如 OpenCV）
            "tools_detected": ["git", "make", "pkg-config"],
            "sys_packages_suggested": [],
            "build_mode": "project_installer",
            "installer_cmd": installer_plan["installer_cmd"],
            "strip_sudo": bool(installer_plan.get("strip_sudo", True)),
            "go_version": go_version,  # 传递 go_version 给 Dockerfile
            "build_mode_name": args.build_mode,  # 新增
        }

        print("📦 准备构建上下文（自带安装器模式）...")
        context_dir = prepare_context(project_info)

        print("📝 生成 Dockerfile...")
        dockerfile_path, dockerfile_content = generate_dockerfile(
            project_info,
            context_dir,
            bin_entries=build_set,
            default_bin=default_bin,
            build_mode=args.build_mode  # 新增参数
        )
        print(f"🧩 Dockerfile 位置: {dockerfile_path}")

        if args.print_dockerfile:
            print("\n===== 生成的 Dockerfile =====\n")
            print(dockerfile_content)
            print("\n===== 结束 Dockerfile =====\n")

        if args.no_build:
            print("⏭ 已按 --no-build 跳过镜像构建。")
            print(f"📁 构建上下文目录: {context_dir}")
            return

        print(f"🚀 使用 docker 构建镜像 {image_name} ...")
        build_image(context_dir, dockerfile_path, image_name, builder="docker")
        print(f"✅ 构建完成：{image_name}")
        
        # 执行后续操作
        _handle_post_build_tasks(args, image_name)
        print("\n⏭ 已按 --prefer-project-installer 跳过运行时动态探测。")
        return

    # 常规：静态分析 +（可选）动态探测
    print(f"🔍 分析项目依赖（模式：{args.build_mode}）...")
    project_info = analyze_project_with_mode(primary_entry, args.project_path, args.build_mode)
    _print_analysis(project_info)  # 确保这里调用的是已定义的 _print_analysis 函数
    
    # 显示模式相关信息
    _print_mode_info(args.build_mode, project_info)

    print("📦 准备构建上下文...")
    context_dir = prepare_context(project_info)

    print("📝 生成 Dockerfile...")
    dockerfile_path, dockerfile_content = generate_dockerfile(
        project_info,
        context_dir,
        bin_entries=build_set,
        default_bin=default_bin,
        build_mode=args.build_mode  # 新增参数
    )
    print(f"🧩 Dockerfile 位置: {dockerfile_path}")

    if args.print_dockerfile:
        print("\n===== 生成的 Dockerfile =====\n")
        print(dockerfile_content)
        print("\n===== 结束 Dockerfile =====\n")

    if args.no_build:
        print("⏭ 已按 --no-build 跳过镜像构建。")
        print(f"📁 构建上下文目录: {context_dir}")
        return

    print(f"🚀 使用 docker 构建镜像 {image_name} ...")
    build_image(context_dir, dockerfile_path, image_name, builder="docker")
    print(f"✅ 构建完成：{image_name}")

    # 执行后续操作（导出、安全扫描）
    _handle_post_build_tasks(args, image_name)

    # 智能动态探测决策
    use_cgo = project_info.get("use_cgo", False)
    should_skip = should_skip_probe(primary_entry)
    run_probe = args.force_probe or (use_cgo and not should_skip)
    
    if should_skip and not args.force_probe:
        print(f"\n⏭ 检测到测试/工具类入口 ({os.path.basename(os.path.dirname(primary_entry))})，跳过动态探测")
        print("   (如需强制探测，使用 --force-probe)")
        return
    elif not run_probe:
        print("\n⏭ 未启用 CGO，默认跳过运行时动态探测（如需强制，使用 --force-probe）。")
        return

    ldd_text = read_ldd_from_image(image_name)
    if ldd_text:
        print("\n===== 运行期依赖（ldd 原文节选） =====")
        lines = ldd_text.splitlines()
        print("\n".join(lines[:30]) + ("\n..." if len(lines) > 30 else ""))

    print("\n🔎 正在执行运行时动态探测（strace）...")
    multi_bin = len(build_set) > 1
    if multi_bin:
        bin_paths = [f"/app/{project_name}/bin/{e['name']}" for e in build_set]
        names = [e["name"] for e in build_set]
    else:
        bin_paths = [f"/app/{project_name}/bin/{project_name}"]
        names = [project_name]

    user_probe_args = shlex.split(args.probe_args) if args.probe_args else None
    def _args_for(_name):
        return user_probe_args if user_probe_args else _parse_probe_args_default(_name)
    args_map = {n: _args_for(n) for n in names}

    probe = probe_runtime_deps(
        image_name=image_name,
        bin_paths=bin_paths,
        names=names,
        args_map=args_map,
        timeout_sec=20,
    )

    print("\n===== 动态探测摘要 =====")
    for item in probe["bins"]:
        print(f"• {item['name']}: exit={item['exit_code']} files={len(item['files_all'])} net={len(item['net'])}")
    if probe.get("errors"):
        print("⚠️  运行中出现问题：", "; ".join(probe["errors"]))

    runtime_pkgs_suggested = aggregate_runtime_packages_from_probe(probe)
    if runtime_pkgs_suggested:
        print(f"\n📦 建议添加的运行期包：{', '.join(runtime_pkgs_suggested)}")
    else:
        print("\n📦 未发现需要额外安装的运行期包。")


def _handle_post_build_tasks(args, image_name: str):
    """处理构建后的任务：导出镜像和安全扫描"""
    
    # 1. 导出镜像文件
    if args.export_image:
        # 确定导出路径
        if args.export_path:
            export_path = args.export_path
        else:
            # 默认在当前目录下，文件名为镜像名（替换特殊字符）
            safe_name = image_name.replace(":", "_").replace("/", "_")
            export_path = f"./{safe_name}.tar"
        
        print(f"📦 正在导出镜像到: {export_path}")
        success = export_image(image_name, export_path)
        if success:
            print(f"✅ 镜像已导出到: {os.path.abspath(export_path)}")
        else:
            print(f"❌ 镜像导出失败")

    # 2. 安全扫描（仅在指定参数时启用）
    if args.security_scan:
        scanner = SecurityScanner()
        
        # 确定报告路径
        if args.security_report_path:
            report_path = args.security_report_path
        else:
            safe_name = image_name.replace(":", "_").replace("/", "_")
            report_path = f"./{safe_name}_security_report.md"
        
        print(f"\n🔒 正在进行安全扫描...")
        success, final_report_path = scanner.scan_and_report(image_name, report_path)
        
        if success:
            print(f"✅ 安全报告已生成: {os.path.abspath(final_report_path)}")
        else:
            print("⚠️  安全扫描未完成（可能因为 trivy 未安装）")


if __name__ == "__main__":
    main()